package biz.datalk.industrialland.common.util;

import biz.datalk.industrialland.common.config.JwtCryptoCfg;
import biz.datalk.industrialland.common.def.CommonConstant;
import biz.datalk.industrialland.common.encrypt.AESTool;
import biz.datalk.industrialland.common.exception.ApplicationException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT 工具类
 *
 * @author tarofang@163.com
 * @date 2019年08月11日
 */
public class JwtUtil {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);

    public static final String PAYLOAD_SYS_USER_ID = "sysUid";
    public static final String PAYLOAD_USERNAME = "username";
    public static final String PAYLOAD_USER_NO = "userNo";

    /** 应用类型 默认值为空 */
    public static final String PAYLOAD_APPKEY = "appkey";
    public static final String[] PAYLOAD_KEYS = ArrayUtils.toArray(PAYLOAD_USERNAME, PAYLOAD_SYS_USER_ID, PAYLOAD_USER_NO);
    public static final String[] PAYLOAD_KEYS_ALL = ArrayUtils.add(PAYLOAD_KEYS, PAYLOAD_APPKEY);

    private static Algorithm getAlgorithm() {
        try {
            KeyPair keyPair = JwtCryptoCfg.getInstance().genKeyPair();
            return getAlgorithm((RSAPublicKey) keyPair.getPublic(), (RSAPrivateKey) keyPair.getPrivate());
        } catch (Exception ex) {
            logger.error("{}", ex.getMessage(), ex);
            throw new ApplicationException(ex);
        }
    }

    /**
     * 校验token是否正确
     *
     * @param token 密钥
     *
     * @return 是否正确
     */
    public static boolean verify(String token, String username) {
        return verify(token, username, StringUtils.EMPTY);
    }

    public static boolean verify(String token, String username, String appkey) {
        appkey = StringUtils.isBlank(appkey) ? "" : appkey.trim();
        try {
            Algorithm algorithm = getAlgorithm();
            String enusername = AESTool.encrypt(username, JwtCryptoCfg.getInstance().getAesKey());
            String enAppkdy = AESTool.encrypt(appkey, JwtCryptoCfg.getInstance().getAesKey());
            JWTVerifier verifier = JWT.require(algorithm).withClaim(PAYLOAD_USERNAME, enusername).withClaim(PAYLOAD_APPKEY, enAppkdy).build();
            // 效验TOKEN
            verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    public static Algorithm getAlgorithm(RSAPublicKey publicKey, RSAPrivateKey rsaPrivateKey) {
        return Algorithm.RSA512(publicKey, rsaPrivateKey);
    }

    public static boolean verify(String token, RSAPublicKey publicKey) {
        try {
            Algorithm algorithm = getAlgorithm(publicKey, null);
            JWT.require(algorithm).build().verify(token);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }

    public static boolean verify(String token) {
        try {
            Algorithm algorithm = getAlgorithm();
            JWT.require(algorithm).build().verify(token);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户名
     */
    public static String getUsername(String token) {
        return getPlayload(token, PAYLOAD_USERNAME);
    }

    public static String getAppkey(String token) {
        return getPlayload(token, PAYLOAD_APPKEY);
    }

    public static String getPlayload(String token, String payloadKey) {
        if (StringUtils.isBlank(token)) {
            return null;
        }
        try {
            DecodedJWT jwt = JWT.decode(token);
            String enUsername = jwt.getClaim(payloadKey).asString();
            return AESTool.decrypt(enUsername, JwtCryptoCfg.getInstance().getAesKey());
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成 token
     * <p>
     * HS256: HMAC using SHA-256
     * HS384: HMAC using SHA-384
     * HS512: HMAC using SHA-512
     * RS256: RSASSA-PKCS-v1_5 using SHA-256
     * RS384: RSASSA-PKCS-v1_5 using SHA-384
     * RS512: RSASSA-PKCS-v1_5 using SHA-512
     * PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
     * PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
     * PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
     * ES256: ECDSA using P-256 and SHA-256
     * ES384: ECDSA using P-384 and SHA-384
     * ES512: ECDSA using P-521 and SHA-512
     *
     * @param username 用户名
     *
     * @return 加密的token
     */
    public static String sign(String username) {
        return sign(username, "");
    }

    public static String sign(String username, String appkey) {
        Date date = new Date(System.currentTimeMillis() + JwtCryptoCfg.getInstance().getExpireMillis());
        Algorithm algorithm = getAlgorithm();

        // 对载荷加密
        String enUsername = AESTool.encrypt(username, JwtCryptoCfg.getInstance().getAesKey());
        appkey = StringUtils.isBlank(appkey) ? "" : appkey.trim();
        String enAppkdy = AESTool.encrypt(appkey, JwtCryptoCfg.getInstance().getAesKey());

        return JWT.create().withClaim(PAYLOAD_USERNAME, enUsername).withClaim(PAYLOAD_APPKEY, enAppkdy).withExpiresAt(date).sign(algorithm);
    }

    /**
     * 根据request中的token获取用户账号
     *
     * @param request {@link HttpServletRequest}
     *
     * @return 用户账号 username
     *
     * @throws ApplicationException
     */
    public static String getUserNameByToken(HttpServletRequest request) throws ApplicationException {
        String accessToken = getRequestToken(request);
        String username = getUsername(accessToken);
        if (StringUtils.isEmpty(username)) {
            throw new ApplicationException("未获取到用户");
        }
        return username;
    }

    public static String getUserNameByToken() throws ApplicationException {
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        return getUserNameByToken(request);
    }


    /**
     * 获取请求的 token
     * <p>
     * 1. 从请求头中获取 token
     * 2. 如果请求头中无 token, 那么从 session中获取 token
     *
     * @param request {@link javax.servlet.http.HttpServletRequest}
     *
     * @return token
     *
     * @author tarofang@163.com
     * @date 2020年01月13日
     */
    public static String getRequestToken(HttpServletRequest request) {
        String token = request.getHeader(CommonConstant.ACCESS_TOKEN);
        if (StringUtils.isBlank(token)) {
            token = (String) WebUtils.getSessionAttribute(request, CommonConstant.ACCESS_TOKEN);
        }
        return token;
    }

    public static String getRequestToken() {
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        return getRequestToken(request);
    }

    public static String getContextPath(HttpServletRequest request) {
        String contextPath = request.getContextPath();
        contextPath = StringUtils.equals(contextPath, "/") ? "" : contextPath;
        return contextPath;
    }

    public static String sign(Map<String, String> payload) {
        JWTCreator.Builder builder = JWT.create();
        String aesKey = JwtCryptoCfg.getInstance().getAesKey();

        String tmp, key, enVal;
        boolean debugEnabled = logger.isDebugEnabled();
        for (Map.Entry<String, String> entry : payload.entrySet()) {
            key = entry.getKey();
            tmp = StringUtils.trim(entry.getValue());
            if (tmp == null) {
                logger.warn("empty payload value. [key={}]", key);
                continue;
            }
            enVal = AESTool.encrypt(tmp, aesKey);
            if (debugEnabled) {
                logger.debug("Jwt sign payload. [key={}, val={}, enVal={}]", key, tmp, enVal);
            }
            builder.withClaim(key, enVal);
        }


        Date expireDate = new Date(System.currentTimeMillis() + JwtCryptoCfg.getInstance().getExpire() * 1000L);
        builder.withExpiresAt(expireDate);

        return builder.sign(getAlgorithm());
    }

    public static boolean verify(String token, Map<String, String> payload) {
        try {
            Algorithm algorithm = getAlgorithm();
            String aesKey = JwtCryptoCfg.getInstance().getAesKey();
            Verification require = JWT.require(algorithm);
            String tmp, key, enVal;
            boolean debugEnabled = logger.isDebugEnabled();
            for (Map.Entry<String, String> entry : payload.entrySet()) {
                key = entry.getKey();
                tmp = StringUtils.trim(entry.getValue());
                if (tmp == null) {
                    logger.warn("empty payload value. [key={}]", key);
                    continue;
                }
                enVal = AESTool.encrypt(tmp, aesKey);
                if (debugEnabled) {
                    logger.debug("Jwt sign payload. [key={}, val={}, enVal={}]", key, tmp, enVal);
                }
                require.withClaim(key, enVal);
            }
            JWTVerifier verifier = require.build();
            verifier.verify(token);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }

    /**
     * 获取 jwt 中的 payload 键值对
     * 默认对键值对进行解密
     *
     * @param token        jwt
     * @param payloadNames 键值对名称
     *
     * @return java.util.Map<java.lang.String, java.lang.String> 指定的键值对
     *
     * @author tarofang@163.com
     * @date 2022年08月24日
     * @see #getPayloads(String, boolean, String...)
     */
    public static Map<String, String> getPayloads(String token, String... payloadNames) {
        return getPayloads(token, Boolean.TRUE, payloadNames);
    }

    /**
     * 获取 jwt 中的 payload 键值对
     *
     * @param token        jwt
     * @param decrypt      true-对键值进行解密， false-不解密
     * @param payloadNames 键值对名称
     *
     * @return java.util.Map<java.lang.String, java.lang.String> 指定的键值对
     *
     * @author tarofang@163.com
     * @date 2022年08月24日
     */
    public static Map<String, String> getPayloads(String token, boolean decrypt, String... payloadNames) {
        if (StringUtils.isBlank(token)) {
            return null;
        }
        if (ArrayUtils.isEmpty(payloadNames)) {
            return null;
        }
        try {
            DecodedJWT jwt = JWT.decode(token);
            Map<String, String> resMap = new HashMap<>();
            String val;
            String aesKey = JwtCryptoCfg.getInstance().getAesKey();
            for (String payloadName : payloadNames) {
                val = jwt.getClaim(payloadName).asString();
                if (val != null && decrypt) {
                    val = AESTool.decrypt(val, aesKey);
                }
                resMap.put(payloadName, val);
            }

            return resMap;
        } catch (JWTDecodeException e) {
            logger.debug("occur exception: {}", e.getMessage(), e);
            return null;
        }
    }

}
